让客户端发现 pod 并与之通信
1、Service 介绍
Service 是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当服务存在时,它的 IP 地址和端口不会改变。客户端通过 IP 地址和端口号建立连接,这些连接会被路由到该服务的任意一个 pod 上。通过这种方式,客户端不需要直到每个单独的提供服务的 pod 的地址,这样这些pod就可以在集群中随时被创建或移除了。
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80 # 该 Service 的端口。
targetPort: 8080 # 该 Service 对应后端容器的端口。
- name: https
poret: 443
targetPort: https # 如果 pod 内部使用了端口命名,可以直接指定端口名称。
selector:
app: kubia # 设置选择器选择 metadata.app=kubia 的 pod 作为后端。
1.1、为什么需要Service?
- 客户端无法提前知道提供服务的 pod 的 IP 地址。K8s 会在 pod 启动前才分配 IP 地址。
- 每个 pod 的生命周期时短暂的,其 IP 地址也是不稳定。
- 水平伸缩意味着一个服务背后有多个 pod 提供相同的服务。
1.2、服务发现
- 环境变量。 K8s 可以自动注入当前svc对应的环境变量(XXX_SERVICE_HOST、XXX_SERVICE_PORT)给新创建的 pod。
- DNS发现服务。 K8s 自带的域名解析服务器 core-dns 可以通过全限定域名(FQDN)解析服务的 IP 地址。K8s 通过修改每个容器的 /etc/resolve.conf 使 core-dns生效。
2、访问集群外部服务
- 方式一:通过手动配置 EndPoints 的方式,可以让 k8s 集群内的客户端像访问集群内的服务一样地访问集群外部服务。
- 方式二:通过配置 ExternalName 类型的 Service,完全绕过 Service 直接访问外部域名。
2.1、EndPoints 介绍
EndPoints 是一种介于 Service 和 Pod 之间的一种资源,它暴露一个服务的 IP 地址和端口的列表。当我们在 Service 的 spec 中定义 pod 选择器时,选择器会动态调整 Service 背后的 Endpoints,从而实现 Service 发现后端 pod 的逻辑。相反,如果我们不为 Service 定义 pod 选择器,那么 K8s 不会创建其对应的 Endpoints,需要手动创建 Endpoints 资源。
apiVersion: v1
kind: Service
metadata:
name: external-service # Endpoints 的名称必须与 Service 的名称匹配。
spec:
ports:
- port: 80
---
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # Endpoints 的名称必须与 Service 的名称匹配。
subsets:
- addresses:
- ip: 11.11.11.11 # Service 将连接重定向到这里的 IP 地址。
- ip: 22.22.22.22
ports:
- port: 80 # 目标端口
2.2、ExternalName 类型的 Service
ExternalName 服务仅在 DNS 级别实施——为服务创建了简单的 CNAME DNS记录,因此连接到此 Service 的客户端将直接连接到外部服务,完全绕过 Service 资源的代理。出于这个原因,这些类型的 Service 不会被配置 ClusterIP。
(PS:CNAME 记录指向完全限定域名而不是数字 IP 地址。)
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName # 上文中未指定类型,则默认为 ClusterIP 类型。
externalName: someapi.somecompany.com
ports:
- port: 80
3、暴露服务给集群外部
将集群内的服务暴露给集群外部的客户端,大致有三种方式:
- NodePort。简单粗暴,每台节点都绑一个固定端口。
- LoadBalance。使用外部的负载均衡器。
- Ingress。七层网络代理。
3.1、使用 NodePort 类型的服务
NodePort 类型的 Service 会在每一个节点开放同一个端口,用户可以通过任意 Node 节点的 IP 地址配合 nodePort 来访问该服务。此方式需要为客户端打开每一台节点上对应端口的防火墙策略,否则用户无法访问。此方法还有一个缺点,就是端口占用数量太多了,一个集群能开放的端口数量就是一台机器的端口数量了。
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80 # ClusterIP 的端口。
targetPort: 8080 # 背后 pod 的实际端口。
nodePort: 30123 # 创建 NodePort 此端口可以不指定,K8s 将随机选择一个端口。
selector:
app: kubia
3.2、通过 LoadBalancer 将服务暴露出来
LoadBalancer 类型的 Service 是一个具有额外的基础设施提供的负载均衡器 NodePort 服务。它也会自动创建一个 nodePort 的端口,但是用户不感知此端口,因此我们不需要配置防火墙规则开放这个端口。当然,如果配置防火墙规则开放了这个 nodePort 端口,那么效果就像前面的 NodePort 类型的 Service 一样。
apiVersion: v1
kind: Service
metadata:
name: kubia-loadbalancer
spec:
type: LoadBalancer # 自动从当前 K8s 集群的基础架构获取负载均衡器。(华为云CCE服务对应华为云ELB服务。)
ports:
- port: 80 # ClusterIP 的端口。
targetPort: 8080 # 背后 pod 的实际端口。
selector:
app: kubia
3.3、通过 Ingress 暴露服务
通过 Ingress 暴露服务的优势:
- 可以通过一个公网IP为多个服务提供访问入口。
- 由于是 7 层网络代理,因此可以提供一些 Service 无法提供的功能(如cookie的会话亲和性等功能)。
实现原理
暂略。
(nginx-ingress-controller主页:https://kubernetes.github.io/ingress-nginx/)
Ingress资源
apiVersion: v1
kind: Ingress
metadata:
name: kubia
spec:
tls: # 集群外部的客户端发起HTTPS请求时,客户端与ingress-controller之间的连接是TLS加密传输的,而ingress-controller到后端的pod之间则是HTTP明文传输的。
- hosts:
- kubia.example.com
secretName: tls-secret
rules:
- host: kubia.example.com # ingress 将域名 kubia.example.com 映射到后端的 Service
http:
paths:
- path: /kubia # 对 kubia.example.com/kubia 的请求转发至 kubia 的 Service
backend:
serviceName: kubia
servicePort: 80
- path: /bar # 对 kubia.example.com/bar 的请求转发至 bar 的 Service
backend:
serviceName: bar
servicePort: 80
- host: bar.example.com
http:
paths:
- path: / # 对 bar.example.com 的请求转发至 bar 的 Service
backend:
serviceName: bar
servicePort: 80
为Ingress创建TLS认证的步骤
# 生成私钥
openssl genrsa -out tls.key 2048
# 生成证书(自签名)
openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=kubia.example.com
# 通过私钥文件和证书文件生成一个Secret
kubectl create secret tls tls-secret --cert-tls.cert --key=tls.key
# 配置 ingress-controller 以及 Ingress 资源
# ...
curl -k -v https://kubia.example.com/kubia
4、pod 就绪后发出信号
TODO
5、使用 headless 服务来发现独立的 pod
TODO
6、排除服务故障经验
TODO
7、Tips
# 双横杠(--)意味着 kubectl 命令项的结束。
# 后面的内容是指在 pod 内部需要执行的命令(否则-s会被解析成kubectl exec的参数)。
# 如果执行的命令没有以横杠开始的参数,则不需要使用双横杠。
kubectl exec kubia-7nog1 -- curl -s http://10.111.249.153
- pod 是否使用内部的 DNS 服务器是根据 pod 中 spec 的 dnsPolicy 属性来决定的。
- ping 一个 svc 的 FQDN 或 clusterIP 是不通的,因为 clusterIP 是一个虚拟 IP,只有在与服务端口结合是才有意义。(PS:11章会解释其工作的原理。)
- JSONPath使用指导:https://kubernetes.io/docs/reference/kubectl/jsonpath/
- 使用
kubectl explain
检查服务的会话亲和性是否设置为None。 - 通过配置 Service 的 spec.externalTrafficPolicy=Local 将外部通信重定向到接受连接的节点上运行的 pod 来阻止额外跳数。此配置引发的两个缺点:
- 必须确保负载平衡器将连接转发给至少具有一个 pod 的节点。
- 使用 local 外部流量策略的服务可能会导致跨 pod 的负载分布不均衡(A节点一个pod,B节点两个pod,则三个pod的流量比为2:1:1)。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nz_nuaa@163.com